import java.io.*;
import java.util.Map;
import java.util.Scanner;
import java.util.NoSuchElementException;
import java.nio.charset.Charset;

/**
 * 
 * @author Jonathan Hsu, Seth Mielke
 * 
 * @modified 8/6/11
 * 
 */
public class OpIO extends Operation {
    private Scanner sc;

	public OpIO(Word[] register, Word[] memory, FileReader programIn,
        FileWriter programOut) {
		super(register, memory, programIn, programOut);

        byte[] delimiterbytes = new byte[] {
            0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
            10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
            20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
            30, 31, 32,
            127, -128, -127, -126, -125, -124, -123, -122, -121, -120, 
            -119, -118, -117, -116, -115, -114, -113, -112, -111, -110, 
            -109, -108, -107, -106, -105, -104, -103, -102, -101, -100, 
            -99, -98, -97, -96
            // Bytes are signed. Below are the unsigned equivalent
            //128, 129, 130, 131, 132, 133, 134, 135, 136,
            //137, 138, 139, 140, 141, 142, 143, 144, 145, 146,
            //147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 
            //157, 158, 159, 160
        };
        String delimiters = "[" +
            new String(delimiterbytes, Charset.forName("ISO-8859-1")) +
            "]+";

        sc = new Scanner(programIn);
        sc.useDelimiter(delimiters);
	}

	/**
	 * Input / Output instruction. If r=0 then consume the longest sequence of
	 * non-separator bytes that form a 'word'. If it is a well formed 20-bit 2's
	 * complement number, place the value in address S(x). If r=1, consume the
	 * first byte, b, at the front of the stream and replace the least
	 * significant 8 bits of register x with b. The S field is ignored. If r=2,
	 * then extend the stream with a well formed character-string decimal
	 * representation of the value encoded at address S(x) according to 20-bit
	 * 2's complement scheme. If r=3 then extend the output stream with the
	 * single byte that is encoded in the 8 most significant bits of register x
	 * according to simple binary. The S field is ignored here.
	 * 
	 * @param pc
	 *            is the program counter, which holds the location of the next
	 *            instruction to be executed
	 * @param traceTable
	 *            is the table that holds the values of of registers and memory
	 *            before and after each instruction execution
	 * @return integer value of the program counter which indicates the location
	 *         of instruction to execute next.
	 */
	public int exec(int pc, Map<String, Word> traceTable) throws IOException {
		// This is taking the address (s(x)) and setting s equal to it
		int sx = memory[pc].s() + memory[pc].x();
		int r = memory[pc].r();
		// this is getting the register that were going to load the memory
		// at position s
		int x = memory[pc].x();

		traceTable.put("Before: Register X #" + Integer.toString(x), register[x].copy());
        if (r == 0 || r == 2) {
            traceTable.put("Before: Memory Location " + String.format("%02x", sx).toUpperCase(),
                            memory[sx].copy());
        }
        
        int num, ch;
        switch (r) {
            case 0:
                try {
                    num = sc.nextInt();
                } catch (NoSuchElementException e) {
                    throw new IOException();
                }
                //System.out.println(num);
                //System.out.println(programIn.read()); // Read one extra character
                memory[sx].set(num);
                //System.out.println(memory[sx].toString());
                break;
            case 1:
                ch = programIn.read();
                if (ch != -1) {
                    register[x].set(register[x].get() & 0xFFFFFF00 | ch);
                }
                break;
            case 2:
                programOut.write(Integer.toString(memory[sx].get()));
                break;
            default: // case 3:
            	//System.out.println(register[x].get() >>> 12 & 0x00FF);
				programOut.write(register[x].get() >>> 12 & 0x000000FF);	
        } 


		traceTable.put("After: Register X #" + Integer.toString(x), register[x]);
        if (r == 0 || r == 2) {
		    traceTable.put("After: Memory Location " + String.format("%02x", sx).toUpperCase(),
				            memory[sx]);
        }

		return pc + 1;
	}
}
